package net.tavion.angelboot.gateway.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import lombok.extern.slf4j.Slf4j;
import net.tavion.angelboot.bsm.dto.SystemKeyInfoDTO;
import net.tavion.angelboot.common.enums.AngelChannel;
import net.tavion.angelboot.common.enums.RequestHeaderElement;
import net.tavion.angelboot.common.enums.SystemCode;
import net.tavion.angelboot.common.extend.SpringExtensionLoader;
import net.tavion.angelboot.common.utils.ObjectMapperUtils;
import net.tavion.angelboot.common.vo.GenericResponse;
import net.tavion.angelboot.framework.logger.rr.WebfluxServerRequestDecorator;
import net.tavion.angelboot.gateway.jwt.JwtProperties;
import net.tavion.angelboot.gateway.jwt.JwtUtil;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Configuration
@Slf4j
public class GatewayCoreAuthFilter implements WebFilter, Ordered {
    ObjectMapper mapper =new ObjectMapper();
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        ServerHttpRequest request =  exchange.getRequest();

        String tokenHeader= request.getHeaders().getFirst(RequestHeaderElement.authorization.getCode());

        if (tokenHeader == null || !tokenHeader.startsWith(JwtUtil.TOKEN_PREFIX) ) {
            return chain.filter(exchange);
        }
        // If there is a token in the request header, parse it and set the authentication information.
        String token = tokenHeader.replace(JwtUtil.TOKEN_PREFIX, "");

        // get spring bean
        StringRedisTemplate stringRedisTemplate = SpringExtensionLoader.getSpringBean(StringRedisTemplate.class);
        JwtUtil jwtUtil = SpringExtensionLoader.getSpringBean(JwtUtil.class);
        JwtProperties jwtProperties = SpringExtensionLoader.getSpringBean(JwtProperties.class);

        // Determine whether token is valid
        Claims claims = null;
        boolean parseJwt=true;
        try {
            claims = jwtUtil.parseJWT(token);
        } catch (SignatureException | MalformedJwtException e){
            //token illegality
            log.warn("The token is illegality:",e);
            parseJwt=false;
            throw e;
        } catch (Exception e){
            //system error
            log.error("Verify Token Exception:",e);
            parseJwt=false;
            throw e;
        }

        String channelCode=null;
        if(parseJwt){
            Map requestChannelMap=(Map)claims.get(JwtUtil.REQUESTCHANNEL);
            SystemKeyInfoDTO systemKeyInfoDTO= new SystemKeyInfoDTO();

            try{
                BeanUtils.populate(systemKeyInfoDTO,requestChannelMap);
            }catch (IllegalAccessException | InvocationTargetException e){
                parseJwt=false;
            }

            if(parseJwt){
                channelCode=systemKeyInfoDTO.getChannelCode();

                if(!AngelChannel.validate(channelCode)){
                    parseJwt=false;
                }
            }
        }

        if(!parseJwt){
            GenericResponse failResponse=new GenericResponse();
            failResponse.setCode(SystemCode.HEADER_ERROR.getCode());
            failResponse.setMessage(SystemCode.HEADER_ERROR.getMessage());
            byte[]  dataBytes= ObjectMapperUtils.writeAsString(failResponse,mapper).getBytes();
            DataBuffer bodyDataBuffer = exchange.getResponse().bufferFactory().wrap(dataBytes);
            return exchange.getResponse().writeWith(Mono.just(bodyDataBuffer));
        }

        // Determine if the token expires
        String userName = claims.getSubject();

        String tokenKey="TOKEN:"+channelCode+":"+userName;

        String redisToken = stringRedisTemplate.opsForValue().get(tokenKey);
        if (StringUtils.isBlank(redisToken) || !redisToken.equals(token)) {
            GenericResponse failResponse=new GenericResponse();
            failResponse.setCode(SystemCode.COMMON_AUTHORIZED_FAILURE.getCode());
            failResponse.setMessage(SystemCode.COMMON_AUTHORIZED_FAILURE.getMessage());
            byte[]  dataBytes= ObjectMapperUtils.writeAsString(failResponse,mapper).getBytes();
            DataBuffer bodyDataBuffer = exchange.getResponse().bufferFactory().wrap(dataBytes);
            return exchange.getResponse().writeWith(Mono.just(bodyDataBuffer));
        }

        String role = (String) claims.get(JwtUtil.ROL);
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
                = new UsernamePasswordAuthenticationToken(userName, null, null);
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);

        // token prolong
        Integer expiredTime = jwtProperties.getExpiredTime();
        if(null == expiredTime || expiredTime <=0 ){
            expiredTime= 30;
        }
        stringRedisTemplate.opsForValue().set(tokenKey ,token, expiredTime, TimeUnit.MINUTES);

        //设置头部信息
        ServerHttpRequest addRequest = exchange.getRequest()
                .mutate().header(RequestHeaderElement.channel.getCode(),channelCode).build()
                .mutate().header(RequestHeaderElement.userId.getCode(),userName).build();

//
//        exchange.getResponse().getHeaders().add(RequestHeaderElement.channel.getCode(),channelCode);
//        exchange.getResponse().getHeaders().add(RequestHeaderElement.userId.getCode(),userName);
        WebfluxServerRequestDecorator requestDecorator=new WebfluxServerRequestDecorator(addRequest);

        ServerWebExchange ex = exchange.mutate()
                .request(requestDecorator)
                .build();


        return chain.filter(ex);
    }

    @Override
    public int getOrder() {
        return 1000;
    }
}